package cn.credit.ms.dbms;

import org.apache.ibatis.builder.StaticSqlSource;
import org.apache.ibatis.exceptions.TooManyResultsException;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.dao.support.DaoSupport;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;


/**
 * MyBatis执行sql工具
 *
 * @author kejun
 * @since 2015-09-10
 * @see org.mybatis.spring.support.SqlSessionDaoSupport
 * @see org.mybatis.spring.mapper.MapperFactoryBean<T>
 * @see org.mybatis.spring.mapper.MapperScannerConfigurer
 * @see com.github.abel533.sql.SqlMapper
 * @see DaoSupport
 */
@Primary
@Component("genericDAO")
public class MybatisDAO extends DaoSupport implements GenericDAO {
    private MSUtils msUtils = new MSUtils();
    private SqlSession sqlSession;
    @Autowired
    private SqlSessionFactory sqlSessionFactory;

 	@Override 
 	protected void checkDaoConfig() throws IllegalArgumentException {
 		Assert.notNull(this.sqlSessionFactory, "Property 'sqlSessionFactory' are required");
 	}

    public long nextSid(){
    	return nextSeq("seq_sid");
    }
    /**
     * 删除数据
     *
     * @param sname sequence Name 
     * @return next seq long 
     */
    public long nextSeq(final String sname){
		String sql = "select nextval(#{name}) as NEXT";		
		Map<String, Serializable> map = getOnly(sql,new Params<String>("name", sname));
		return (map==null)?-1:((Number)map.get("NEXT")).longValue();

    }
    private long getAutoSid(){
		String sql = "SELECT LAST_INSERT_ID() as SID";		
		Map<String, Serializable> map = getOnly(sql);
		return (map==null)?-1:((Number)map.get("SID")).longValue();

    }
    
    
    /**
     * 获取List中最多只有一个的数据
     *
     * @param list List结果
     * @param <T>  泛型类型
     * @return
     */
    private <T> T getOne(List<T> list) {
        if (list.size() == 1) {
            return list.get(0);
        } else if (list.size() > 1) {
            throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
            return null;
        }
    }

    /**
     * 查询返回一个结果，多个结果时抛出异常
     *
     * @param sql 执行的sql
     * @return
     */
    @Override
    public Map<String, Serializable> getOnly(String sql) {
        List<Map<String, Serializable>> list = getList(sql);
        return getOne(list);
    }

    /**
     * 查询返回一个结果，多个结果时抛出异常
     *
     * @param sql   执行的sql
     * @param value 参数
     * @return
     */
    @Override
    public Map<String, Serializable> getOnly(String sql, Object value) {
        List<Map<String, Serializable>> list = getList(sql, value);
        return getOne(list);
    }

    /**
     * 查询返回一个结果，多个结果时抛出异常
     *
     * @param sql        执行的sql
     * @param resultType 返回的结果类型
     * @param <T>        泛型类型
     * @return
     */
    public <T> T selectOne(String sql, Class<T> resultType) {
        List<T> list = selectList(sql, resultType);
        return getOne(list);
    }

    /**
     * 查询返回一个结果，多个结果时抛出异常
     *
     * @param sql        执行的sql
     * @param value      参数
     * @param resultType 返回的结果类型
     * @param <T>        泛型类型
     * @return
     */
    public <T> T selectOne(String sql, Object value, Class<T> resultType) {
        List<T> list = selectList(sql, value, resultType);
        return getOne(list);
    }

    /**
     * 查询返回List<Map<String, Object>>
     *
     * @param sql 执行的sql
     * @return
     */
	@Override
    public List<Map<String, Serializable>> getList(String sql) {
        String msId = msUtils.select(sql);
        return sqlSession.selectList(msId);
    }

    /**
     * 查询返回List<Map<String, Object>>
     *
     * @param sql   执行的sql
     * @param value 参数
     * @return
     */
    @Override
    public List<Map<String, Serializable>> getList(String sql, Object value) {
        Class<?> parameterType = value != null ? value.getClass() : null;
        String msId = msUtils.selectDynamic(sql, parameterType);
        return sqlSession.selectList(msId, value);
    }

    /**
     * 查询返回指定的结果类型
     *
     * @param sql        执行的sql
     * @param resultType 返回的结果类型
     * @param <T>        泛型类型
     * @return
     */
    public <T> List<T> selectList(String sql, Class<T> resultType) {
        String msId;
        if (resultType == null) {
            msId = msUtils.select(sql);
        } else {
            msId = msUtils.select(sql, resultType);
        }
        return sqlSession.selectList(msId);
    }

    /**
     * 查询返回指定的结果类型
     *
     * @param sql        执行的sql
     * @param value      参数
     * @param resultType 返回的结果类型
     * @param <T>        泛型类型
     * @return
     */
    public <T> List<T> selectList(String sql, Object value, Class<T> resultType) {
        String msId;
        Class<?> parameterType = value != null ? value.getClass() : null;
        if (resultType == null) {
            msId = msUtils.selectDynamic(sql, parameterType);
        } else {
            msId = msUtils.selectDynamic(sql, parameterType, resultType);
        }
        return sqlSession.selectList(msId, value);
    }

    /**
     * 插入数据
     *
     * @param sql 执行的sql
     * @return
     */
    public int insert(String sql) {
        String msId = msUtils.insert(sql);
        return sqlSession.insert(msId);
    }

    /**
     * 插入数据
     *
     * @param sql   执行的sql
     * @param value 参数
     * @return
     */
    public int insert(String sql, Object value) {
        Class<?> parameterType = value != null ? value.getClass() : null;
        String msId = msUtils.insertDynamic(sql, parameterType);
        return sqlSession.insert(msId, value);
    }
    public long insertAndGetSid(String sql, Object value) {
        Class<?> parameterType = value != null ? value.getClass() : null;
        String msId = msUtils.insertDynamic(sql, parameterType);
        if(sqlSession.insert(msId, value)>0){
        	return getAutoSid();
        }
        return 0;
    }
    /**
     * 更新数据
     *
     * @param sql 执行的sql
     * @return
     */
    public int update(String sql) {
        String msId = msUtils.update(sql);
        return sqlSession.update(msId);
    }

    /**
     * 更新数据
     *
     * @param sql   执行的sql
     * @param value 参数
     * @return
     */
    public int update(String sql, Object value) {
        Class<?> parameterType = value != null ? value.getClass() : null;
        String msId = msUtils.updateDynamic(sql, parameterType);
        return sqlSession.update(msId, value);
    }

    /**
     * 删除数据
     *
     * @param sql 执行的sql
     * @return
     */
    public int delete(String sql) {
        String msId = msUtils.delete(sql);
        return sqlSession.delete(msId);
    }

    /**
     * 删除数据
     *
     * @param sql   执行的sql
     * @param value 参数
     * @return
     */
    public int delete(String sql, Object value) {
        Class<?> parameterType = value != null ? value.getClass() : null;
        String msId = msUtils.deleteDynamic(sql, parameterType);
        return sqlSession.delete(msId, value);
    }

    private class MSUtils {
    	private String dataSourceId;
        private Configuration configuration;
        private LanguageDriver languageDriver;

        private MSUtils() {  }

        private MSUtils(Configuration configuration) {
        	this.init(configuration);
        }

        private void init() {
    		checkDaoConfig();
    		sqlSession = new SqlSessionTemplate(sqlSessionFactory);
            this.configuration = sqlSession.getConfiguration();
            this.languageDriver = configuration.getDefaultScriptingLanuageInstance();
       }
        private void init(Configuration configuration) {
            this.configuration = configuration;
            this.languageDriver = configuration.getDefaultScriptingLanuageInstance();
        }

        private void checkSession(){
        	String threadDataSource = ThreadKeys.getDS();
          	if( (threadDataSource!=null && !threadDataSource.equals(dataSourceId))
          	  ||(this.languageDriver==null) ){
           		dataSourceId = threadDataSource;
         		init();
          	}
         }

        private void checkSession_old(){
        	if(this.languageDriver==null){
        		MybatisDAO owner = MybatisDAO.this;
        		owner.checkDaoConfig();
                SqlSession sqlSession = new SqlSessionTemplate(owner.sqlSessionFactory);
                owner.sqlSession = sqlSession;
                this.configuration =sqlSession.getConfiguration();
                this.languageDriver = configuration.getDefaultScriptingLanuageInstance();
        	}
        }

        /**
         * 创建MSID
         *
         * @param sql 执行的sql
         * @param sql 执行的sqlCommandType
         * @return
         */
        private String newMsId(String sql, SqlCommandType sqlCommandType) {
            StringBuilder msIdBuilder = new StringBuilder(sqlCommandType.toString());
            //msIdBuilder.append(".").append(sql.hashCode());
            //Cache key issue. if diff sql has same hashCode will throw new IllegalArgumentException(name + " already contains value for " + key);
            //@see org.apache.ibatis.session.Configuration#802  
            msIdBuilder.append(".").append(sql);
            return msIdBuilder.toString();
        }

        /**
         * 是否已经存在该ID
         *
         * @param msId
         * @return
         */
        private boolean hasMappedStatement(String msId) {
        	checkSession();
            //return configuration.hasStatement(msId);// org.apache.ibatis.session.Configuration#801 ...already contains value for... 
            return configuration.hasStatement(msId, false);//fail-fast when concurrent.
        }
        
        /**
         * 创建一个简单的MS
         * @param msId
         * @param sqlSource      执行的sqlSource
         * @param sqlCommandType 执行的sqlCommandType
         * @param resultType 返回的结果类型
          */
        private void newMappedStatement(String msId, SqlSource sqlSource, SqlCommandType sqlCommandType, final Class<?> resultType){
        	checkSession();
            MappedStatement ms = new MappedStatement.Builder(configuration, msId, sqlSource, sqlCommandType).resultMaps(new ArrayList<ResultMap>() {{
            	add(new ResultMap.Builder(configuration, "defaultResultMap", resultType, new ArrayList<ResultMapping>(0)).build());}
            }).build();
        	try{
        		configuration.addMappedStatement(ms);//缓存
        	}catch(IllegalArgumentException e){
        		logger.info("MybatisDAO concurrent statement cache, already exists.");
        	}
        }
        
        private String select(String sql) {
            String msId = newMsId(sql, SqlCommandType.SELECT);
            if (hasMappedStatement(msId)) {
                return msId;
            }
            StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);
            newMappedStatement(msId, sqlSource, SqlCommandType.SELECT, UpperKeyMap.class);
            return msId;
        }

        private String selectDynamic(String sql, Class<?> parameterType) {
            String msId = newMsId(sql + parameterType, SqlCommandType.SELECT);
            if (hasMappedStatement(msId)) {
                return msId;
            }
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);
            newMappedStatement(msId, sqlSource, SqlCommandType.SELECT, UpperKeyMap.class);
            return msId;
        }

        private String select(String sql, Class<?> resultType) {
            String msId = newMsId(resultType + sql, SqlCommandType.SELECT);
            if (hasMappedStatement(msId)) {
                return msId;
            }
            StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);
            newMappedStatement(msId, sqlSource, SqlCommandType.SELECT, resultType);
            return msId;
        }

        private String selectDynamic(String sql, Class<?> parameterType, Class<?> resultType) {
            String msId = newMsId(resultType + sql + parameterType, SqlCommandType.SELECT);
            if (hasMappedStatement(msId)) {
                return msId;
            }
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);
            newMappedStatement(msId, sqlSource, SqlCommandType.SELECT, resultType);
            return msId;
        }

        private String insert(String sql) {
            String msId = newMsId(sql, SqlCommandType.INSERT);
            if (hasMappedStatement(msId)) {
                return msId;
            }
            StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);
            newMappedStatement(msId, sqlSource, SqlCommandType.INSERT,int.class);
            return msId;
        }

        private String insertDynamic(String sql, Class<?> parameterType) {
            String msId = newMsId(sql + parameterType, SqlCommandType.INSERT);
            if (hasMappedStatement(msId)) {
                return msId;
            }
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);
            newMappedStatement(msId, sqlSource, SqlCommandType.INSERT,int.class);
            return msId;
        }

        private String update(String sql) {
            String msId = newMsId(sql, SqlCommandType.UPDATE);
            if (hasMappedStatement(msId)) {
                return msId;
            }
            StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);
            newMappedStatement(msId, sqlSource, SqlCommandType.UPDATE,int.class);
            return msId;
        }

        private String updateDynamic(String sql, Class<?> parameterType) {
            String msId = newMsId(sql + parameterType, SqlCommandType.UPDATE);
            if (hasMappedStatement(msId)) {
                return msId;
            }
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);
            newMappedStatement(msId, sqlSource, SqlCommandType.UPDATE,int.class);
            return msId;
        }

        private String delete(String sql) {
            String msId = newMsId(sql, SqlCommandType.DELETE);
            if (hasMappedStatement(msId)) {
                return msId;
            }
            StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);
            newMappedStatement(msId, sqlSource, SqlCommandType.DELETE,int.class);
            return msId;
        }

        private String deleteDynamic(String sql, Class<?> parameterType) {
            String msId = newMsId(sql + parameterType, SqlCommandType.DELETE);
            if (hasMappedStatement(msId)) {
                return msId;
            }
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);
            newMappedStatement(msId, sqlSource, SqlCommandType.DELETE,int.class);
            return msId;
        }
    }

	@Override
	public int execute(String sql) {
		return execute(sql,null);
	}

	@Override
	public int execute(String sql, Object value) {
		String commandType = sql.trim().substring(0,6);
		if("update".equalsIgnoreCase(commandType)){
			return update(sql, value);
		}else if("insert".equalsIgnoreCase(commandType)){
			return insert(sql, value);
		}else if("delete".equalsIgnoreCase(commandType)){
			return delete(sql, value);
		}
		throw new IllegalArgumentException("execute SQL shoud be insert, update oe delete.");
	}


}


/*public class BasicMapper extends SqlMapper {
	public BasicMapper(SqlSession sqlSession) {
		super(sqlSession);
	}
 }*/